/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package core;

import domain.Domain;
import expression.Alias;
import expression.And;
import expression.Eq;
import expression.Variable;
import expression.IntExpr;
import expression.Leq;
import expression.Lt;
import expression.Max;
import expression.Min;
import expression.Neq;
import expression.Not;
import expression.Or;
import expression.Sum;
import expression.Value;
import expression.VarInDomain;
import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author ddoose
 */
public class Node {
    public static enum RESULT { SAT, UNSAT, UNKNOWN };
    /**
     * Répertorie les variables
     */
    private List<Integer> variables;
    /**
     * Liste des alias
     */
    private List<Integer> alias;
    /**
     * Répertorie les expressions entières du problème (dont les variables).
     */
    private List<IntExpr> expressions;
    /**
     * Liste des paramètres de toutes les expressions entière.
     */
    private Matrix next;
    /**
     * Dependences entres les expressions entière (identifiée par leur entrée dans la table.
     */
    private Matrix prev;
    /**
     * Point d'entrée du problème.
     */
    private int constraint;
    /**
     * Problem
     */
    private Problem problem;
    /**
     * liste des IntExpr a propager
     */
    private List<Integer> propagations;
    /**
     * liste des IntExpr a evaluer
     */
    private List<Integer> evaluations;
    
    public Node(Problem p, Node n) {
        problem = p;
        variables = new ArrayList<Integer>();
        alias = new ArrayList<Integer>();
        expressions = new ArrayList<IntExpr>();
        next = new Matrix();
        prev = new Matrix();
        constraint = -1;
        propagations = new ArrayList<Integer>();
        evaluations = new ArrayList<Integer>();
    }
    
    /*
     *   ----------------------------------------
     *                  DEPENDENCIES
     *   ----------------------------------------
     */
    public void addDependecy(int p, int n) {
        next.addElement(p, n);
        prev.addElement(n, p);
    }
    
    public List<Integer> getNexts(int id) {
        return next.getLine(id);
    }
    
    public List<Integer> getPrevs(int id) {
        return prev.getLine(id);
    }
    
    private boolean sameLine(List<Integer> i, List<Integer> j) {
        if (i.size() != j.size()) {
            return false;
        }
        
        for (int k = 0; k < i.size(); k++) {
            int ii = i.get(k);
            int jj = j.get(k);
            if (ii != jj) {
                return false;
            }
        }
        
        return true;
    }
    
    public boolean sameNext(int i, int j) {
        return sameLine(next.getLine(i), next.getLine(j));
    }
        
    public boolean samePrev(int i, int j) {
        return sameLine(prev.getLine(i), prev.getLine(j));
    }
    
    private void removeNexts(int i) {
        for (int n: next.getLine(i)) {
            prev.removeElement(n, i);
        }
        next.getLine(i).clear();
    }
    
    private void replaceNexts(int a, int b) {
        for (int p: prev.getLine(a)) {
            next.replace(p, a, b);
        }
    }
    
    private void addPrevs(int a, int b) {
        for (int p: prev.getLine(a)) {
            if (!prev.getLine(b).contains(p)) {
                prev.addElement(b, p);
            }
        }
    }
    
    /*
     *   ----------------------------------------
     *             VARIABLES et ALIAS
     *   ----------------------------------------
     */
    public List<Variable> getVariables() {
        List<Variable> vars = new ArrayList<Variable>();
        for (int i: variables) {
            Variable v = (Variable)expressions.get(i);
            vars.add(v);
        }
        return vars;
    }
    
    public Variable getVariable(String n) {
        for (int i: variables) {
            Variable v = (Variable)expressions.get(i);
            if (n.equals(v.getName())) {
                return v;
            }
        }
        return null;
    }
    
    public Variable createVariable(String n) {
        Variable v = getVariable(n);
        if (v == null) {
            v = new Variable(this, n);
            variables.add(v.getId());
            addIntExpr(v);
        }
        return v;
    }
    
    public List<Alias> getAlias() {
        List<Alias> al = new ArrayList<Alias>();
        for (int i: alias) {
            Alias a = (Alias)expressions.get(i);
            al.add(a);
        }
        return al;
    }
    
    public Alias getAlias(String n) {
        for (int i: alias) {
            Alias a = (Alias)expressions.get(i);
            if (n.equals(a.getName())) {
                return a;
            }
        }
        return null;
    }
    
    public Alias createAlias(String n, IntExpr e) {
        Alias a = getAlias(n);
        if (a == null) {
            a = new Alias(this, n, e);
            alias.add(a.getId());
            addIntExpr(a);
        }
        return a;
    }
    
    /*
     *   ----------------------------------------
     *                  INT EXPRESSION
     *   ----------------------------------------
     */
    public int getId() {
        
        for (int i = 0; i < expressions.size(); i++) {
            if (expressions.get(i) == null) {
                return i;
            }
        }
        
        return expressions.size();
    }
    
    public IntExpr getIntExpr(int id) {
        return expressions.get(id);
    }
    
    private IntExpr getSame(IntExpr i) {
        for (IntExpr e: expressions) {
            if (i.same(e)) {
                return e;
            }
        }
        return null;
    }
    
    private IntExpr addIntExpr(IntExpr i) {
        IntExpr same = getSame(i);
        if (same != null) {
            return same;
        }
        else {
            if (i.getId() >= expressions.size()) {
                expressions.add(i);
                prev.addLine();
                next.addLine();
            }
            else {
                expressions.set(i.getId(), i);
            }
            return i;
        }
    }

    public Value createValue(int v) {
        return (Value)addIntExpr(new Value(this, v));
    }

    public Leq createLeq(IntExpr a, IntExpr b) {
        IntExpr[] temp = {a, b};
        return (Leq)addIntExpr(new Leq(this, temp));
    }
    
    public Lt createLt(IntExpr a, IntExpr b) {
        IntExpr[] temp = {a, b};
        return (Lt)addIntExpr(new Lt(this, temp));
    }
    
    public Eq createEq(IntExpr a, IntExpr b) {
        IntExpr[] temp = {a, b};
        return (Eq)addIntExpr(new Eq(this, temp));
    }
    
    public Neq createNeq(IntExpr a, IntExpr b) {
        IntExpr[] temp = {a, b};
        return (Neq)addIntExpr(new Neq(this, temp));
    }
    
    public Sum createSum(IntExpr[] p) {
        return (Sum)addIntExpr(new Sum(this, p));
    }
    
    public Sum createSum(IntExpr[] p, int v) {
        return (Sum)addIntExpr(new Sum(this, p, v));
    }
    
    public And createAnd(IntExpr[] p) {
        return (And)addIntExpr(new And(this, p));
    }
    
    public Or createOr(IntExpr[] p) {
        return (Or)addIntExpr(new Or(this, p));
    }
    
    public Not createNot(IntExpr p) {
        return (Not)addIntExpr(new Not(this, p));
    }

    public VarInDomain createVarInDomain(Variable v, Domain d) {
        return (VarInDomain)addIntExpr(new VarInDomain(this, v, d));
    }
    
    public Max createMax(IntExpr[] p) {
        return (Max)addIntExpr(new Max(this, p));
    }
    
    public Min createMin(IntExpr[] p) {
        return (Min)addIntExpr(new Min(this, p));
    }
            
    /*
     *   ----------------------------------------
     *                  NEW CONSTAINT
     *   ----------------------------------------
     */
    public void addConstraint(IntExpr e) {
        IntExpr[] temp = {e, expressions.get(constraint)};
        And and = createAnd(temp);
        constraint = and.getId();
    } 

    public void setConstraint(IntExpr i) {
        constraint = i.getId();
    }

    /*
     *   ----------------------------------------
     *             REPLACE REMOVE...
     *   ----------------------------------------
     */
    private void cutNext(int id) {
        for (int n: next.getLine(id)) {
            prev.removeElement(n, id);
            
            if (prev.getLine(n).isEmpty()) {
                cutNext(n);
                if (!(expressions.get(n) instanceof Variable)) {
                    expressions.set(n, null);
                }
            }
        }
        next.getLine(id).clear();
    }
    
    public void replaceWithValue(IntExpr old, int value) {
        cutNext(old.getId());
        Value v = new Value(this, value);
        v.setId(old.getId());
        expressions.set(old.getId(), v);        
    }

    /*
     *   ----------------------------------------
     *                  SOLVE
     *   ----------------------------------------
     */
    public RESULT solve() {
        try {
            getIntExpr(constraint).reduceTarget(Domain.TRUE);
            while (!propagations.isEmpty() || !evaluations.isEmpty()) {
                propagate();
                eval();
            }
        } catch (UnsatException e) {
//            System.out.println("Node UNSAT 1");
            return RESULT.UNSAT;
        }

        if (expressions.get(constraint).getCurrent().isFalse()) {
//            System.out.println("Node UNSAT 2");
            return RESULT.UNSAT;
        }
        else if (expressions.get(constraint).getCurrent().isTrue()) {
//            System.out.println("Node SAT");
            return RESULT.SAT;
        }
        else {
//            System.out.println("Node UNKNOWN");
//            reduce();
            return RESULT.UNKNOWN;
        }
    }
    
    private void reduce() {
        boolean b = true;
        
        while (b) {
            b = false;
            for (int i = 0; i < expressions.size(); i++) {
                IntExpr e = expressions.get(i);
                if ((e != null) && e.reduce()) {
                    b = true;
                }
            }
        }
    }
    
    /*
     *   ----------------------------------------
     *             REDUCE: INT EXPRESSION
     *   ----------------------------------------
     */
    public void removeDependency(int p, int n) {
        next.removeElement(p, n);
        prev.removeElement(n, p);
        if (prev.getLine(n).isEmpty()) {
            removeIntExpr(expressions.get(n));
        }
    }
    

    
    public void replaceIntExpr(int a, int b) {
        replaceNexts(a, b);
        addPrevs(a, b);        
        prev.getLine(a).clear();
        if (constraint == a) {
            constraint = b;
        }
        expressions.set(a, null);
    }
    
    /**
     * Remplace une IntExpr par sa/ses suivante(s).
     * @param e
     */
    public void removeIntExpr(IntExpr e) {
        IntExpr temp = expressions.get(next.getLine(e.getId()).get(0));
        
        // les previous
        for (int p: prev.getLine(e.getId())) {
            // ajout du prev
            prev.addElement(temp.getId(), p);
            // modification des suivants des precedents
            next.replace(p, e.getId(), temp.getId());
            
        }
        // les suivants
        for (int p: next.getLine(e.getId())) {
            if (p != temp.getId()) {
                // ajout du prev
                next.addElement(temp.getId(), p);
                // modification des precedents des suivants
                prev.replace(p, e.getId(), temp.getId());
            }
        }
        
        // suppression de e
        prev.removeElement(temp.getId(), e.getId());
        next.getLine(e.getId()).clear();
        prev.getLine(e.getId()).clear();
        expressions.set(e.getId(), null);
        if (constraint == e.getId()) {
            constraint = temp.getId();
        }
    }
    
    /*
     *   ----------------------------------------
     *             PROPAGATION EVALUATIONS
     *   ----------------------------------------
     */
    public void addPropagation(int id) {
        propagations.add(id);
    }
    
    private void propagate() throws UnsatException {    
        while (!propagations.isEmpty()) {
            IntExpr e = expressions.get(propagations.get(0));
            propagations.remove(0);
            
            if (e != null) {
                e.propagate();
            }
        }        
    }
    
    public void addEvaluation(int id) {
        if (!evaluations.contains(id)) {
            evaluations.add(id);
        }
    }
    
    private void eval() throws UnsatException {    
        while (!evaluations.isEmpty()) {
            IntExpr e = expressions.get(evaluations.get(0));
            evaluations.remove(0);
            
            if (e != null) {
                e.eval();
            }
        }        
    }
    
    /*
     *   ----------------------------------------
     *                  CLONE
     *   ----------------------------------------
     */
    public Node nextNode() {
        Node node = new Node(problem, this);
        
        // Variables
        node.variables = new ArrayList<Integer>();
        for (int i: variables) {
            node.variables.add(i);
        }
        
        // Alias
        node.alias = new ArrayList<Integer>();
        for (int i: alias) {
            node.alias.add(i);
        }
        
        // Expressions
        node.expressions = new ArrayList<IntExpr>();
        for (IntExpr e: expressions) {
            if (e == null) {
                node.expressions.add(null);
            }
            else {
                node.expressions.add(e.clone(node));
            }
        }
        
        // Next
        node.next = next.clone();
        
        // Prev
        node.prev = prev.clone();
        
        // Constraint
        node.constraint = constraint;
        
        return node;
    }

    /*
     *   ----------------------------------------
     *                  VARIABLES
     *   ----------------------------------------
     */
    @Override
    public String toString() {
        String s = "variables :\n";
        for (int i: variables) {
            Variable v = (Variable)expressions.get(i);
            s += "\t" + v.pretty() + "\n";
        }
        
        s += "alias :\n";
        for (int i: alias) {
            Alias a = (Alias)expressions.get(i);
            s += "\t" + a.pretty() + "\n";
        }

        s += "\nexpressions :\n";
        for (int i = 0; i < expressions.size(); i++) {
            IntExpr e = expressions.get(i);
            if (e != null) {
                s += "\t" + e.pretty() + "\n";
            }
        }
        
        s += "\nconstraint = " + constraint + "\n";
        
        return s;
    }
}
